Skip to content

Say only the audit events this deployment can actually record - #540

Open
Hotragn wants to merge 1 commit into
CopilotKit:mainfrom
Hotragn:say-only-what-this-deployment-can-record
Open

Hotragn wants to merge 1 commit into
CopilotKit:mainfrom
Hotragn:say-only-what-this-deployment-can-record

Conversation

@Hotragn

@Hotragn Hotragn commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

What this changes

Four event types are declared in auditEventTypes and written by nothing in server/src:

Type
connector.sync_succeeded server/src/connectors is an empty directory
connector.sync_failed same
knowledge.searched server/src/knowledge is an empty directory; git log -S shows it arriving in #113 and its only writer leaving in #118
agent.invoked no writer at any point I can find

They stayed for months because the taxonomy test named them explicitly, so the list kept agreeing with itself rather than with the code.

This removes them, and adds the guard that would have caught it.

Where it runs

server/src/audit.ts — the declaration only. No writer changes, because there are none to change.

Boundary and audit

This is the audit trail, so the reason is the operator's, not the type checker's.

The Audit screen filters by event type. Filtering for knowledge.searched returns an empty page, and an empty page in an audit trail reads as this did not happen rather than this cannot happen. Those are very different answers to give someone checking whether a search touched a document, and the record currently cannot distinguish them. A trail that can say nothing is worse than one that says nothing, because the reader cannot tell which they are looking at.

Reading old rows is unaffected, and I checked rather than assumed:

  • The Audit UI does not enumerate auditEventTypes. It uses the curated REFUSED_EVENT_TYPES and DID_NOT_HAPPEN_EVENT_TYPES groups, and neither names any of the four.
  • AuditEvent.eventType is a plain string, not the union, so a historical row carrying one of these still loads and renders.

So this narrows what the type is allowed to claim, not what the table is allowed to hold — which is the right direction for a record you are not allowed to rewrite.

Changelog

Not user-facing — no operator can currently see these types produce anything, which is the bug.

Proof

The new test, declares nothing this deployment cannot write, reads the event types off server/src rather than off a hand-kept list, because a hand-kept list is exactly what just failed. Run against main and against this branch:

--- main ---
declared: 70
unwritable: [ "connector.sync_succeeded", "connector.sync_failed",
  "knowledge.searched", "agent.invoked" ]

--- this branch ---
declared: 66
unwritable: []

It fails on the bug and passes on the fix, which is the only property a guard has to have.

It matches literal strings only. That is deliberate: every writer today passes the type as a literal, and a writer that computed one would fail here and should — an event type a reader cannot grep for is worse than this test being strict.

I could not run bun test server/tests/audit.test.ts end to end on this machine. The file imports createApp, and my node_modules is stale — cron-parser, @ag-ui/mastra and @mastra/client-js are declared in package.json but absent locally, so the import fails before any test runs. I confirmed this failure is identical on unmodified main, and ran the guard's logic standalone against both trees to produce the numbers above. bunx tsc --noEmit in server/ reports only those same three missing packages and nothing from these files.

What is not covered

  • The reverse direction — a type written in server/src but missing from the taxonomy — is not checked here. It is a real gap, but catching it means recognising writer call sites rather than grepping strings, and that is its own change.
  • I have not tried to restore the connector or knowledge features. If either comes back, its author re-adds the type in the same commit as the writer, which is the order this guard now enforces.

🤖 Generated with Claude Code

The audit taxonomy is the trail's vocabulary, and a name in it reads as a promise that this
deployment can produce that row. Four of them could not.

`connector.sync_succeeded`, `connector.sync_failed`, `knowledge.searched` and `agent.invoked` are
declared in `auditEventTypes` and written by nothing in `server/src`. They outlived the code that
wrote them — `server/src/knowledge` and `server/src/connectors` are both empty directories now, and
`git log -S` shows `knowledge.searched` arriving in CopilotKit#113 and its only writer leaving in CopilotKit#118. They
stayed because the taxonomy test named them explicitly, so the list kept agreeing with itself.

That is not cosmetic for an operator. The Audit screen filters by type, so filtering for
`knowledge.searched` returns an empty page, and an empty page in an audit trail reads as "this did
not happen" rather than "this cannot happen". A record that can say nothing is worse than a record
that says nothing, because the reader cannot tell which one they are looking at.

Removing them is safe to read back: the Audit UI does not enumerate `auditEventTypes` — it uses the
curated `REFUSED_EVENT_TYPES` and `DID_NOT_HAPPEN_EVENT_TYPES` groups, and neither names these —
and `AuditEvent.eventType` is a plain `string`, so any historical row still loads and renders. This
narrows what the type is allowed to claim, not what the table is allowed to hold.

The guard is the point of the change. `declares nothing this deployment cannot write` reads the
event types off `server/src` rather than off a hand-kept list, because a hand-kept list is exactly
what just failed. It matches literal strings only: every writer today passes the type as a literal,
and a writer that computed one would fail here and should — an event type a reader cannot grep for
is worse than this test being strict.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant